package tazadum.engine;

import java.io.InputStream;
import java.util.Comparator;
import java.util.Random;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;

import javazoom.jl.decoder.JavaLayerException;
import javazoom.jl.player.JavaSoundAudioDevice;
import javazoom.jl.player.advanced.AdvancedPlayer;
import javazoom.jl.player.advanced.PlaybackEvent;
import javazoom.jl.player.advanced.PlaybackListener;
import tazadum.engine.debug.ParameterManager;
import tazadum.engine.event.SyncEventQueue;
import tazadum.engine.scene.Scene;
import tazadum.util.Pair;

public class Engine {
	private static final Comparator<Pair<Float, ?>> SCENE_COMPARATOR = new Comparator<Pair<Float, ?>>() {
		@Override
		public int compare(final Pair<Float, ?> o1, final Pair<Float, ?> o2) {
			return Float.compare(o1.first(), o2.first());
		}
	};

	private final ParameterManager parameters;
	private long _time0 = 0;

	private final SyncEventQueue eventQueue = new SyncEventQueue();
	private final SortedSet<Pair<Float, Scene>> scenes = new TreeSet<Pair<Float, Scene>>(SCENE_COMPARATOR);

	private Scene loadingScene = null;

	private State state = State.LoadingResources;
	private AdvancedPlayer _player;

	private Thread musicThread;

	public Engine() {
		parameters = new ParameterManager();
	}

	public ParameterManager getParameterManager() {
		return parameters;
	}

	public SyncEventQueue eventQueue() {
		return eventQueue;
	}

	public Random getRandom() {
		return new Random(0);
	}

	public void loadingScene(final Scene loadingScene) {
		this.loadingScene = loadingScene;
	}

	public void setMusicStream(final InputStream musicStream) {
		try {
			final JavaSoundAudioDevice audioDevice = new JavaSoundAudioDevice();
			_player = new AdvancedPlayer(musicStream, audioDevice);
			_player.setPlayBackListener(new PlaybackListener() {
				@Override
				public void playbackStarted(final PlaybackEvent evt) {
					_time0 = getTime();
					state = State.Run;
				}
			});
		} catch (final JavaLayerException e) {
			throw new RuntimeException(e);
		}
	}

	public void setDebug(final boolean debug) {
		parameters.setDebug(debug);
		if (debug) {
			parameters.show();
		}
	}

	public void add(final float priority, final Scene scene) {
		scenes.add(Pair.create(priority, scene));
	}

	public void shutdown() {
		if (_player != null) {
			_player.stop();
		}
	}

	public void init(final GLAutoDrawable drawable) {
		doBackgroundLoading(drawable.getGL());

		for (final Pair<?, Scene> entry : scenes) {
			entry.second().init(this, drawable, eventQueue);
		}
	}

	public void display(final GLAutoDrawable drawable) {
		final float time = getScaledTime();

		switch (state) {
		case LoadingResources:
			// display loading scene
			if (loadingScene != null) {
				loadingScene.display(drawable, eventQueue, time);
			}
			break;
		case WaitingForAudio:
			playMusic();
			break;
		case Run:
			// display scenes
			for (final Pair<?, Scene> entry : scenes) {
				entry.second().display(drawable, eventQueue, time);
			}
			break;
		}
		drawable.getGL().glFlush();
	}

	private void playMusic() {
		if (musicThread == null) {
			musicThread = new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						_player.play();
					} catch (final JavaLayerException e) {
						throw new RuntimeException(e);
					}
				}
			}, "Music thread");
			musicThread.start();
		}
	}

	private long getTime() {
		return System.nanoTime();
	}

	private float getScaledTime() {
		return (getTime() - _time0) * 0.000000001f;
	}

	/**
	 * Start background loading of resources and computations
	 * 
	 * @param gl
	 */
	private void doBackgroundLoading(final GL gl) {
		final Thread thread = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					for (final Pair<?, Scene> entry : scenes) {
						entry.second().loadResources(Engine.this, eventQueue);
					}
				} finally {
					_time0 = getTime();
					state = (_player != null) ? State.WaitingForAudio : State.Run;
				}
			};
		}, "Background Loading");
		thread.start();
	}
}
